/******************************************************************************
 *      Mestrado em Ciência da Computação - UFMS
 *		Redes de Computadores - Prof. Dr. Ronaldo Alves Ferreira
 *		Alunos: Geraldo Landre, Marcel Tolentino, Vinícius Bueno
 *****************************************************************************/

#include "access.h"
#include "parser.h"

int get_path(vstring *line,header_data *hd)
{
    int index = -1;
    //printf("\n\nlinha inicial da request: $$$%s$$$\n\n",line->pt);
    // garantindo que todas as requisições começarão com www.
    if((index = get_index_occour(&line,"://")) >= 0)
        remove_substring(&line,0,index + strlen("://"));

    //printf("\n\nlinha request: $$$%s$$$\n\n",line->pt);
    if((index = get_index_occour(&line,SLASH)) >= 0)
    {
        copy_substring(&(hd->srv_request),line,index,line->length - index);
        remove_occour(&line,hd->srv_request->pt);
        remove_occour(&line,SLASH);
    }
    else
        copy_string(&(hd->srv_request),SLASH);

    if((index = get_index_occour(&line,COLON)) >= 0)
    {
        remove_occour(&line,COLON);
        copy_substring(&(hd->srv_port),line,index,line->length);
        remove_occour(&line,hd->srv_request->pt);
    }
    else
        free_string(&(hd->srv_port));

    concat_vstring(&(hd->srv_url),line);
    copy_vstring(&(hd->request),hd->srv_url);

    if(hd->srv_request->length > 1)
        concat_vstring(&(hd->request),hd->srv_request);
    //TODO: verificar tratativa dos arquivos locais: favicon.ico
    return 1;
}

int parse_first_line(vstring **line,header_data *hd)
{
    vstring *value = NULL;
    int resp_code = 0;

    while(*line != NULL)
    {
        trim(line);
        get_token(line,&value,SPACE);
        if(get_method(&value) > 0) // só retorna maior que 0 se for um método
        {
            hd->method = get_method(&value);
            copy_vstring(&(hd->method_str),value);
        }
        else
            if(strstr(value->pt,"HTTP/1.0") != NULL || strstr(value->pt,"HTTP/1.1") != NULL)
                copy_vstring(&(hd->http),value);
            else
                if(string_to_integer(value,&resp_code)) // se foi transformado em número, é o código de um cabeçalho de resposta
                {
                    hd->error = resp_code;
                    free_string(line);
                }
                else
                    get_path(value,hd);
    }
    return 1;
}

int parse_parameter(vstring **line,header_data **hd)
{
    vstring *copy_line = NULL;
    copy_vstring(&copy_line,*line);
    
    switch(get_parameter(line))
    {
        case V_END: // 0, indicando que acabou o cabeçalho
            (*hd)->end = 1;
            break;
        case V_AGE: // 6
            copy_vstring(&((*hd)->header[V_AGE]),copy_line);
            (*hd)->age = get_number(line);
            break;
        case V_ALLOW: // 7
            copy_vstring(&((*hd)->header[V_ALLOW]),copy_line);
            get_allow(line,(*hd)->allow);
            break;
        case V_CACHE_CONTROL: // 9
            copy_vstring(&((*hd)->header[V_CACHE_CONTROL]),copy_line);
            (*hd)->cache_params = get_cache_control(line,(*hd)->is_response);
            break;
        case V_CONNECTION: // 10
            copy_vstring(&((*hd)->header[V_CONNECTION]),copy_line);
            (*hd)->alive = get_connection(line);
            break;
        case V_CONTENT_LENGTH: // 13
            copy_vstring(&((*hd)->header[V_CONTENT_LENGTH]),copy_line);
            (*hd)->content_length = get_number(line);
            //printf("\npegou o content_length: %i\n",(*hd)->content_length);
            break;
        case V_CONTENT_LOCATION: // 14
            copy_vstring(&((*hd)->header[V_CONTENT_LOCATION]),copy_line);
            copy_vstring(&((*hd)->content_location),*line);
            break;
        case V_DATE: // 18
            copy_vstring(&((*hd)->header[V_DATE]),copy_line);
            (*hd)->date = get_date(line);
            break;
        case V_EXPIRES:
            copy_vstring(&((*hd)->header[V_EXPIRES]),copy_line);
            (*hd)->expires = get_date(line);
            break;
        case V_HOST: // 23
            copy_vstring(&((*hd)->header[V_HOST]),copy_line);
            get_host(line,hd);
            break;
        case V_LAST_MODIFIED: // 29
            copy_vstring(&((*hd)->header[V_LAST_MODIFIED]),copy_line);
            break;
        case V_LOCATION: // 30
            copy_vstring(&((*hd)->header[V_LOCATION]),copy_line);
            break;
        case V_REFERER: // 36
            copy_vstring(&((*hd)->header[V_REFERER]),copy_line);
            //get_referer(line,hd);
            break;
        case V_TRANSFER_ENCODING: // 41
            copy_vstring(&((*hd)->header[V_TRANSFER_ENCODING]),copy_line);
            (*hd)->chunked = get_transfer_encoding(line);
            break;
        case V_UNKNOW:
            break;
    }
    free_string(&copy_line);
    return 0;
}

int is_first_line(vstring *line)
{
    return (line != NULL && strstr(line->pt,"HTTP/1.") != NULL);
}

int parse(vstring *msg,header_data **hd)
{
    vstring *line = NULL;

    printf("cabeçalho no parser:\n\n");
    print_string(msg);
    printf("\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
    while(!(*hd)->end && !is_null(&msg) && get_line(&line,&msg))
    {
        if(is_first_line(line)) // primeira linha da primeira parte da mensagem
            parse_first_line(&line,*hd);
        else
            parse_parameter(&line,hd);
        free_string(&line);
    }
    return (!(*hd)->error || (*hd)->error == MSG_OK);
}

/**
 * Fun��o respons�vel por criar a mensagem a ser enviada para o servidor
 * Por enquanto ela s� modifica a FIRSTLINE para efetuar o GET
 * @param hd
 */
vstring* create_srv_msg(header_data **hd)
{
    //int i;
    vstring *msg = NULL;

    // criar a first_line
    copy_vstring(&msg,(*hd)->method_str);
    concat_string(&msg,SPACE);

    if((*hd)->srv_request->length > 1 && (*hd)->srv_request->pt[0] != '/')
        concat_string(&msg,SLASH);
    //printf("mensagem sendo criada %i: %s\n\n\n",(*hd)->srv_request->length,(*hd)->srv_request->pt);
    concat_vstring(&msg,(*hd)->srv_request);
    concat_string(&msg,SPACE);
    concat_vstring(&msg,(*hd)->http);
    concat_string(&msg,END_HEADER_LINE);

    // criando linha Host:
    concat_string(&msg,"Host: ");
    concat_vstring(&msg,(*hd)->srv_url);
    if((*hd)->srv_port != NULL)
    {
        concat_string(&msg,COLON);
        concat_vstring(&msg,(*hd)->srv_port);
    }    
    concat_string(&msg,END_HEADER_LINE);
    concat_string(&msg,"Proxy-Connection: keep-alive");
    concat_string(&msg,END_HEADER_LINE);

    // final de cabeçalho*/
    concat_string(&msg,END_HEADER_LINE);

    return msg; // retornar a mensagem
}

int get_from_origin_server(header_data **client)
{
    int sockfd = 0,iSc = 0;
    vstring *err = NULL,*msg = NULL;
    header_data *new_hd = NULL;
    struct addrinfo *res = NULL;

    // criar a mensagem a ser enviada para o servidor

    msg = create_srv_msg(client);
    printf("\nconnecting... socket do cliente(%i)\n\n",(*client)->client_socket);
   //print_string(msg);
    //print_header_data(*client);

    if((sockfd = srv_connect((*client)->srv_url,(*client)->srv_port,err,&res)) < 0)
    {
        (*client)->error = ERR_GATEWAY_TIME_OUT;
        return 0; // false
    }
    printf("connected!\n");

    // enviar requisição que o cliente fez para o servidor (SEND)
    iSc = send(sockfd,msg->pt,msg->length,0);
    if(iSc == -1)
    {
        (*client)->error = ERR_GATEWAY_TIME_OUT;
        return 0; // false    
    }

    //printf("vai criar um novo header_data para parsear a resposta\n\n");
    // receber resposta do servidor (RECV)
    new_hd = new_header();

    copy_header_data(client,&new_hd);
    new_hd->is_response = 1;
    new_hd->end = 0;
    free_string(&(new_hd->msg));
    printf("chamada do server recv para receber resposta\n\n\n");
    server_pass(sockfd,&new_hd);
    
    // devolvendo o cabeçalho novo
    free(*client);
    *client = new_hd;
    // libera recursos criados na função srv_connect (getaddrinfo)
    freeaddrinfo(res);

    return 1;
}

void miss_cache(header_data **hd)
{
    //level1 *new_file = NULL;
    //time_t now;

    while(!(*hd)->error || (*hd)->error == ERR_MOVED_PERMANENTLY || (*hd)->error == ERR_FOUND)
    {
        if((*hd)->error == ERR_MOVED_PERMANENTLY || (*hd)->error == ERR_FOUND)
        {            
            free_string(&((*hd)->srv_port));
            free_string(&((*hd)->srv_request));
            free_string(&((*hd)->srv_url));            
            get_path((*hd)->header[V_LOCATION],*hd);
        }
        get_from_origin_server(hd);
    }
    printf("saindo do miss cache\n");
        
    /*if(is_cacheable(*hd))
    {
        create_level1(&new_file);
        create_meta_data(&(new_file->data));
        create_name_file(&(new_file->data->file_name));
        now = time(NULL);
        new_file->data->last_access = localtime(&now);
        new_file->data->came_from_server = localtime(&now);
        new_file->data->size = (*hd)->msg->length;
        copy_vstring(&(new_file->file_data),(*hd)->msg);
        copy_vstring(&(new_file->data->request),(*hd)->request);        
        pthread_rwlock_unlock(&cache_semaphore);
        pthread_rwlock_wrlock(&cache_semaphore);
        insert_in_cache(&new_file);
        pthread_rwlock_unlock(&cache_semaphore);
    }*/
}

int handle_request(header_data **hd) // vindo o cabeçalho completo
{
    //int found = 0;
    vstring *request = NULL;
    
    copy_vstring(&request,(*hd)->request);
    
    if(has_permission(request))
    {
        miss_cache(hd);    
    }
    else
        (*hd)->error = ERR_FORBIDDEN;
    
    /*pthread_rwlock_rdlock(&cache_semaphore);
    
    if(has_permission(request))
    {
        if((found = search_request_level1(request)) != 0) // se deu hit na cache
        {
            hit_level1(found,&((*hd)->msg));
            pthread_rwlock_unlock(&cache_semaphore);
            (*hd)->error = MSG_OK;
        }
        else
        {
            if((found = search_request_level2(request)) != 0) // se deu hit na cache
            {
                hit_level2(found,&((*hd)->msg));
                pthread_rwlock_unlock(&cache_semaphore);
                (*hd)->error = MSG_OK;
            }
            else
                miss_cache(hd);
        }
    }
    else
        (*hd)->error = ERR_FORBIDDEN;*/
    
    return (!(*hd)->error ||(*hd)->error == MSG_OK);
}

vstring* get_header(header_data **hd)
{
    vstring *header = NULL,*len = NULL;

    if((*hd)->http != NULL)
        copy_vstring(&header,(*hd)->http);
    else
        copy_string(&header,"HTTP/1.1");
    concat_string(&header,SPACE);
    concat_string(&header,get_str_httperror((*hd)->error));
    concat_string(&header,END_HEADER_LINE);

    concat_string(&header,"Server: FACOM-MCC-RC-2011/1.0\r\n");
    concat_string(&header,"Content-type: ");
    if((*hd)->srv_request != NULL && get_extension((*hd)->srv_request->pt) != NULL)
        concat_string(&header,get_extension((*hd)->srv_request->pt));
    else
        concat_string(&header,"text/html");
    if(get_index_occour(&header,"text/html") < 0 || get_index_occour(&header,"*/*") >= 0)
        concat_string(&header,"; charset=utf-8");
    
    concat_string(&header,END_HEADER_LINE);   
    
    if((*hd)->content_length > 0)// && (*hd)->content_length < SIZE_CHUNK)
    {
        concat_string(&header,"Content-Length: ");
        len = new_vstring();
        integer_to_string(&len,(*hd)->content_length);
        concat_vstring(&header,len);
        free_string(&len);
    }
    else
    {
        //(*hd)->chunked = 1;
        concat_string(&header,"Transfer-Encoding: chunked");
    }
    
    concat_string(&header,END_HEADER_LINE);
    
    if((*hd)->alive)
        concat_string(&header,"Connection: keep-alive\r\n");
    else
        concat_string(&header,"Connection: close\r\n");
    concat_string(&header,END_HEADER_LINE);
    
    return header;
}

vstring* get_html_error(int error,char* custom_message)
{
	vstring *html = NULL;

        copy_string(&html,"<head><h1>");
        concat_string(&html,get_str_httperror(error));
	concat_string(&html,"</h1></head><body><table>");
	concat_string(&html,"<tr><td>");

	if(custom_message != NULL)
            concat_string(&html,custom_message);
	else
            concat_string(&html,get_str_httperror(error));

	concat_string(&html,"</tr></td></table></body>\n");

	return html;
}

vstring* get_msg_error(header_data **hd, char* custom_message)
{
    vstring *header = NULL;

    (*hd)->msg = get_html_error((*hd)->error,custom_message);

    header = get_header(hd);
    concat_vstring(&header,(*hd)->msg);

    return header;
}